﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Bmz.Studio.Phone.Domain;

namespace Bmz.Studio.Phone.EntityFramework
{
    /// <summary>
    /// Implements IRepository for Entity Framework.
    /// </summary>
    /// <typeparam name="TDbContext">DbContext which contains <see cref="TEntity"/>.</typeparam>
    /// <typeparam name="TEntity">Type of the Entity for this repository</typeparam>
    /// <typeparam name="TPrimaryKey">Primary key of the entity</typeparam>
    public class EfRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity
    {
        private StudioDbContext _StudioDbContext { get; set; }

        /// <summary>
        /// Gets EF DbContext object.
        /// </summary>
        protected virtual DbContext Context
        {
            get
            {
                if (_StudioDbContext == null)
                    lock (this)
                        if (_StudioDbContext == null)
                            _StudioDbContext = new StudioDbContext();
                return _StudioDbContext;
            }
        }

        /// <summary>
        /// Gets DbSet for given entity.
        /// </summary>
        protected virtual DbSet<TEntity> Table { get { return Context.Set<TEntity>(); } }


        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="dbContextProvider"></param>
        public EfRepository()
        {
        }

        public IQueryable<TEntity> GetAll()
        {
            return Table;
        }

        public TEntity Insert(TEntity entity)
        {
            return Table.Add(entity);
        }

        public int InsertAndGetId(TEntity entity)
        {
            entity = Insert(entity);
            Context.SaveChanges();
            return entity.Id;
        }

        public TEntity FirstOrDefault(int id)
        {
            return GetAll().FirstOrDefault(CreateEqualityExpressionForId(id));
        }

        public TEntity InsertOrUpdate(TEntity entity)
        {
            return EqualityComparer<int>.Default.Equals(entity.Id, default(int))
                ? Insert(entity)
                : Update(entity);
        }

        public int InsertOrUpdateAndGetId(TEntity entity)
        {
            entity = InsertOrUpdate(entity);

            if (entity.IsTransient())
            {
                Context.SaveChanges();
            }

            return entity.Id;
        }

        public TEntity Update(TEntity entity)
        {
            AttachIfNot(entity);
            Context.Entry(entity).State = EntityState.Modified;
            return entity;
        }

        public void Delete(TEntity entity)
        {
            AttachIfNot(entity);
            Table.Remove(entity);
        }

        public void Delete(int id)
        {
            var entity = Table.Local.FirstOrDefault(ent => EqualityComparer<int>.Default.Equals(ent.Id, id));
            if (entity == null)
            {
                entity = FirstOrDefault(id);
                if (entity == null)
                {
                    return;
                }
            }

            Delete(entity);
        }

        protected void AttachIfNot(TEntity entity)
        {
            if (!Table.Local.Contains(entity))
            {
                Table.Attach(entity);
            }
        }

        protected Expression<Func<TEntity, bool>> CreateEqualityExpressionForId(int id)
        {
            var lambdaParam = Expression.Parameter(typeof(TEntity));

            var lambdaBody = Expression.Equal(
                Expression.PropertyOrField(lambdaParam, "Id"),
                Expression.Constant(id, typeof(int))
                );

            return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
        }
    }
}
